//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.4
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
//
// C# Port port by: Lars Brubaker
//                  larsbrubaker@gmail.com
// Copyright (C) 2007
//
// Permission to copy, use, modify, sell and distribute this software 
// is granted provided this copyright notice appears in all copies. 
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
//          mcseemagg@yahoo.com
//          http://www.antigrain.com
//----------------------------------------------------------------------------
using System;
using System.Collections.Generic;

using AGG.Image;
using AGG.VertexSource;
using AGG.Transform;
using AGG.RasterizerScanline;

namespace AGG
{
    public class ImageGraphics2D : Graphics2D
    {
        const int cover_full = 255;
        protected IScanlineCache m_ScanlineCache;

        public ImageGraphics2D()
        {
        }

        public ImageGraphics2D(IImageByte destImage, ScanlineRasterizer rasterizer, IScanlineCache scanlineCache)
            : base(destImage, rasterizer)
        {
            m_ScanlineCache = scanlineCache;
        }

        public override IScanlineCache ScanlineCache
        {
            get { return m_ScanlineCache; }
            set { m_ScanlineCache = value; }
        }

        public override void SetClippingRect(rect_d clippingRect)
        {
            Rasterizer.SetVectorClipBox(clippingRect);
        }

        public override void Render(IVertexSource vertexSource, int pathIndexToRender, RGBA_Bytes colorBytes)
        {
            m_Rasterizer.reset();
            Affine transform = GetTransform();
            if (!transform.is_identity())
            {
                vertexSource = new VertexSourceApplyTransform(vertexSource, transform);
            }
            m_Rasterizer.add_path(vertexSource, pathIndexToRender);
            if (m_DestImage != null)
            {
                agg_renderer_scanline.Default.render_scanlines_aa_solid(m_DestImage, m_Rasterizer, m_ScanlineCache, colorBytes);
            }
            else
            {
                agg_renderer_scanline.Default.RenderSolid(m_DestImageFloat, m_Rasterizer, m_ScanlineCache, colorBytes.GetAsRGBA_Floats());
#warning get the color as floats
            }
        }

        PathStorage drawImageRectPath = new PathStorage();
        ScanlineCachePacked8 drawImageScanlineCache = new ScanlineCachePacked8();

        void DrawImage(IImageByte sourceImage,
            double DestX, double DestY,
            double HotspotOffsetX, double HotspotOffsetY,
            double ScaleX, double ScaleY,
            double AngleRad,
            RGBA_Bytes Color32,
            ref rect_d pFinalBlitBounds,
            bool doDrawing,
            bool oneMinusSourceAlphaOne,
            bool useNearestNeighborFilter)
        {
            Affine destRectTransform = Affine.NewIdentity();

            if (HotspotOffsetX != 0.0f || HotspotOffsetY != 0.0f)
            {
                destRectTransform *= Affine.NewTranslation(-HotspotOffsetX, -HotspotOffsetY);
            }

            if (ScaleX != 1 || ScaleY != 1)
            {
                destRectTransform *= Affine.NewScaling(ScaleX, ScaleY);
            }

            if (AngleRad != 0)
            {
                destRectTransform *= Affine.NewRotation(AngleRad);
            }

            if (DestX != 0 || DestY != 0)
            {
                destRectTransform *= Affine.NewTranslation(DestX, DestY);
            }

            int SourceBufferWidth = (int)sourceImage.Width;
            int SourceBufferHeight = (int)sourceImage.Height;

            drawImageRectPath.remove_all();

            drawImageRectPath.move_to(0, 0);
            drawImageRectPath.line_to(SourceBufferWidth, 0);
            drawImageRectPath.line_to(SourceBufferWidth, SourceBufferHeight);
            drawImageRectPath.line_to(0, SourceBufferHeight);
            drawImageRectPath.ClosePolygon();


            // Calculate the bounds. LBB [10/5/2004]
            const int ERROR_ADD = 0;
            double BoundXDouble, BoundYDouble;
            BoundXDouble = 0; BoundYDouble = 0;
            destRectTransform.transform(ref BoundXDouble, ref BoundYDouble);
            double BoundX = (double)BoundXDouble;
            double BoundY = (double)BoundYDouble;

            pFinalBlitBounds.Left = Math.Floor(BoundX - ERROR_ADD);
            pFinalBlitBounds.Right = Math.Ceiling(BoundX + ERROR_ADD);
            pFinalBlitBounds.Top = Math.Floor(BoundY - ERROR_ADD);
            pFinalBlitBounds.Bottom = Math.Ceiling(BoundY + ERROR_ADD);

            BoundXDouble = SourceBufferWidth; BoundYDouble = 0;
            destRectTransform.transform(ref BoundXDouble, ref BoundYDouble);
            BoundX = (double)BoundXDouble;
            BoundY = (double)BoundYDouble;
            pFinalBlitBounds.Left = Math.Min((long)Math.Floor(BoundX - ERROR_ADD), pFinalBlitBounds.Left);
            pFinalBlitBounds.Right = Math.Max((long)Math.Ceiling(BoundX + ERROR_ADD), pFinalBlitBounds.Right);
            pFinalBlitBounds.Top = Math.Min((long)Math.Floor(BoundY - ERROR_ADD), pFinalBlitBounds.Top);
            pFinalBlitBounds.Bottom = Math.Max((long)Math.Ceiling(BoundY + ERROR_ADD), pFinalBlitBounds.Bottom);

            BoundXDouble = SourceBufferWidth; BoundYDouble = SourceBufferHeight;
            destRectTransform.transform(ref BoundXDouble, ref BoundYDouble);
            BoundX = (double)BoundXDouble;
            BoundY = (double)BoundYDouble;
            pFinalBlitBounds.Left = Math.Min((long)Math.Floor(BoundX - ERROR_ADD), pFinalBlitBounds.Left);
            pFinalBlitBounds.Right = Math.Max((long)Math.Ceiling(BoundX + ERROR_ADD), pFinalBlitBounds.Right);
            pFinalBlitBounds.Top = Math.Min((long)Math.Floor(BoundY - ERROR_ADD), pFinalBlitBounds.Top);
            pFinalBlitBounds.Bottom = Math.Max((long)Math.Ceiling(BoundY + ERROR_ADD), pFinalBlitBounds.Bottom);

            BoundXDouble = 0; BoundYDouble = SourceBufferHeight;
            destRectTransform.transform(ref BoundXDouble, ref BoundYDouble);
            BoundX = (double)BoundXDouble;
            BoundY = (double)BoundYDouble;
            pFinalBlitBounds.Left = Math.Min((long)Math.Floor(BoundX - ERROR_ADD), pFinalBlitBounds.Left);
            pFinalBlitBounds.Right = Math.Max((long)Math.Ceiling(BoundX + ERROR_ADD), pFinalBlitBounds.Right);
            pFinalBlitBounds.Top = Math.Min((long)Math.Floor(BoundY - ERROR_ADD), pFinalBlitBounds.Top);
            pFinalBlitBounds.Bottom = Math.Max((long)Math.Ceiling(BoundY + ERROR_ADD), pFinalBlitBounds.Bottom);

            if (!doDrawing)
            {
                return;
            }

            if (m_DestImage.OriginOffset.x != 0 || m_DestImage.OriginOffset.y != 0)
            {
                destRectTransform *= Affine.NewTranslation(-m_DestImage.OriginOffset.x, -m_DestImage.OriginOffset.y);
            }

            Affine sourceRectTransform = new Affine(destRectTransform);
            // We invert it because it is the transform to make the image go to the same position as the polygon. LBB [2/24/2004]
            sourceRectTransform.invert();

            AGG.span_allocator spanAllocator = new span_allocator();

            ImageBufferAccessorClip source = new ImageBufferAccessorClip(sourceImage, RGBA_Doubles.rgba_pre(0, 0, 0, 0).GetAsRGBA_Bytes());
            span_interpolator_linear interpolator = new span_interpolator_linear(sourceRectTransform);
            ISpanGenerator spanImageFilter = null;
            if (useNearestNeighborFilter)
            {
                spanImageFilter = new span_image_filter_rgba_nn(source, interpolator);
            }
            else
            {
                spanImageFilter = new span_image_filter_rgba_bilinear_clip(source, RGBA_Doubles.rgba_pre(0, 0, 0, 0), interpolator);
            }

#if false
            ScanlineRasterizer rasterizer = new ScanlineRasterizer();
            rasterizer.SetVectorClipBox(0, 0, m_DestImage.Width(), m_DestImage.Height());
#else
            ScanlineRasterizer rasterizer = this.Rasterizer;
#endif
            VertexSourceApplyTransform transfromedRect = new VertexSourceApplyTransform(drawImageRectPath, destRectTransform);
            rasterizer.add_path(transfromedRect);

#if false
	        bool HighQualityFilter = (BlitXParams.m_OptionalFlags & CBlitXParams::BlitHighQualityFilter) != 0
		        && (BlitXParams.m_OptionalFlags & CBlitXParams::RenderOneMinusScrAlpha_One) == 0;
	        if (HighQualityFilter)
	        {
		        static agg::image_filter_lut filter;
		        static bool BuiltLUT = false;
		        if (!BuiltLUT)
		        {
			        filter.calculate(agg::image_filter_blackman(4), true);
			        BuiltLUT = true;
		        }
		        typedef agg::span_image_filter_rgba<src_accessor_type, interpolator_type> span_gen_type;

		        CFrame PremultFrame;
		        PremultFrame.Initialize(sourceImage->GetWidth(), sourceImage->GetHeight(), 32);
		        PremultFrame.Fill(CColor(255,0,255,255));
		        const byte* pSrcBuffer = sourceImage->GetBuffer();
		        const ulong* pSrcOffsets = sourceImage->GetYTable();
		        byte* pDestBuffer = PremultFrame.GetBuffer();
		        ulong* pDestOffsets = PremultFrame.GetYTable();

		        const Pixel32* pSrcRow = (const Pixel32*)(pSrcBuffer);
		        Pixel32* pDestRow = (Pixel32*)(pDestBuffer);
		        uint NumPixels = sourceImage->GetHeight() * sourceImage->GetWidth();
		        for (uint i=0; i<NumPixels; i++)
		        {
			        if( (*((ulong*)&Color32) & 0xFFFFFFFF) == 0xFFFFFFFF)
			        {
				        pDestRow[i].Alpha = pSrcRow[i].Alpha;
				        pDestRow[i].Red = (pSrcRow[i].Red*pSrcRow[i].Alpha + 255) >> 8;
				        pDestRow[i].Green = (pSrcRow[i].Green*pSrcRow[i].Alpha + 255) >> 8;
				        pDestRow[i].Blue = (pSrcRow[i].Blue*pSrcRow[i].Alpha + 255) >> 8;
			        }
			        else
			        {
				        pDestRow[i].Alpha = (pSrcRow[i].Alpha*Color32.Alpha + 255) >> 8;
				        pDestRow[i].Red = (Color32.Red*pSrcRow[i].Red*pDestRow[i].Alpha + 65535) >> 16;
				        pDestRow[i].Green = (Color32.Green*pSrcRow[i].Green*pDestRow[i].Alpha + 65535) >> 16;
				        pDestRow[i].Blue = (Color32.Blue*pSrcRow[i].Blue*pDestRow[i].Alpha + 65535) >> 16;
			        }
		        }
		        agg::rendering_buffer colored_premultiplied_buffer;
		        colored_premultiplied_buffer.attach(PremultFrame.GetBuffer(), PremultFrame.GetWidth(), PremultFrame.GetHeight(), (int)PremultFrame.GetScanWidth() * (int)PremultFrame.GetBytesPerPixel());
		        agg::pixfmt_bgra32 premult_pixfmt(colored_premultiplied_buffer);
		        src_accessor_type premult_image(premult_pixfmt, agg::rgba(0,0,0,0));
		        if(BlitXParams.m_OptionalFlags & CBlitXParams::RenderCorrectDestAlpha)
		        {
			        CRenderScanlinesFiltered<pixfmt_bgra32_premult_src_respect_dest_alpha, span_gen_type>::Draw(&DestBuffer, &premult_image, &spanAllocator, &interpolator, &rasterizer, &scanlineCache, pClippingRect, &filter);
		        }
		        else
		        {
			        CRenderScanlinesFiltered<pixfmt_bgra32_premult_src_ignore_dest_alpha, span_gen_type>::Draw(&DestBuffer, &premult_image, &spanAllocator, &interpolator, &rasterizer, &scanlineCache, pClippingRect, &filter);
		        }
	        }
	        else 
#endif
            {
                ImageClippingProxy destImageWithClipping = new ImageClippingProxy(m_DestImage);
                agg_renderer_scanline.Default.GenerateAndRender(rasterizer, drawImageScanlineCache, destImageWithClipping, spanAllocator, spanImageFilter);
#if false
		        if(oneMinusSourceAlphaOne)
		        {
			        CRenderScanlines<agg::pixfmt_bgra32_add_blend_respect_alpha, span_gen_type>::Draw(&DestBuffer, &src_image, Color32, &spanAllocator, &interpolator, &rasterizer, &scanlineCache, pClippingRect);
		        }
		        else
		        {
			        CRenderScanlines<agg::pixfmt_bgra32_mult_blend_respect_alpha, span_gen_type>::Draw(&DestBuffer, &src_image, Color32, &spanAllocator, &interpolator, &rasterizer, &scanlineCache, pClippingRect);
		        }
#endif
            }
        }

        public override void Render(IImageByte source,
            double x, double y,
            double angleDegrees,
            double inScaleX, double inScaleY,
            RGBA_Bytes color,
            BlendMode renderingMode)
        {
            rect_d m_OutFinalBlitBounds = new rect_d();
            //const int ALPHA_CHANNEL_BITS_DIVISOR = 5;

            double scaleX = inScaleX;
            double scaleY = inScaleY;

            double HotspotOffsetX = 0;
            double HotspotOffsetY = 0;

#if true
            Affine transform = GetTransform();
            if (!transform.is_identity())
            {
                if (scaleX != 1 || scaleY != 1)// || angleDegrees != 0)
                {
                    throw new NotImplementedException();
                }
                // <BUG> make this do rotation and scalling
                transform.transform(ref x, ref y);
            }
#endif

#if true
            // TODO: <BUG> make this do rotation and scalling
            rect_i sourceBounds = source.GetBounds();
            sourceBounds.Offset((int)x, (int)y);
            rect_i destBounds = this.m_DestImage.GetBounds();

            if (!rect_i.DoIntersect(sourceBounds, destBounds))
            {
                if (scaleX != 1 || scaleY != 1)// || angleDegrees != 0)
                {
                    throw new NotImplementedException();
                }
                return;
            }
#endif

#if false
	        MaxAlphaFrameProperty maxAlphaFrameProperty = MaxAlphaFrameProperty::GetMaxAlphaFrameProperty(source);

	        if((maxAlphaFrameProperty.GetMaxAlpha() * color.A_Byte) / 256 <= ALPHA_CHANNEL_BITS_DIVISOR)
	        {
		        m_OutFinalBlitBounds.SetRect(0,0,0,0);
	        }
#endif

            if (source == m_DestImage || scaleX <= 0 || scaleY <= 0)
            {
                m_OutFinalBlitBounds.SetRect(0, 0, 0, 0);
            }

#if false // all our buffers our 32
	        if(source.GetBitDepth() != 32)
	        {
		        int HSX = source.GetUpperLeftOffsetX();
		        int HSY = source.GetUpperLeftOffsetY();
		        CFrame Temp;
		        Temp.Initialize(source.GetWidth(), source.GetHeight(), 32);
		        Temp.CFrameInterface::Blit(source, -HSX, -HSY, &BlitNormal);
		        assert(SafeCast(source, CFrame));
		        ((CFrame*)source).Initialize(&Temp);
		        ((CFrame*)source).SetUpperLeftOffsetX(HSX);
		        ((CFrame*)source).SetUpperLeftOffsetY(HSY);
	        }
#endif

            //*(Pixel32*)&g_BlitTransColorBlend = m_Color.GetColor32();

            bool IsScaled = (scaleX != 1 || scaleY != 1);
            bool IsRotated = (Math.Abs(angleDegrees) > 0.1);

            //bool IsMipped = false;
            double sourceOriginOffsetX = source.OriginOffset.x;
            double sourceOriginOffsetY = source.OriginOffset.y;
            bool CanUseMipMaps = IsScaled;
            if (scaleX > 0.5 || scaleY > 0.5)
            {
                CanUseMipMaps = false;
            }

            bool useNearestNeighborFilter = !IsScaled && !IsRotated && x == (int)x && y == (int)y;
#if false
	        if(CanUseMipMaps)
	        {
		        CMipMapFrameProperty* pMipMapFrameProperty = CMipMapFrameProperty::GetMipMapFrameProperty(source);
		        double OldScaleX = scaleX;
		        double OldScaleY = scaleY;
		        const CFrameInterface* pMippedFrame = pMipMapFrameProperty.GetMipMapFrame(ref scaleX, ref scaleY);
		        if(pMippedFrame != source)
		        {
			        IsMipped = true;
			        source = pMippedFrame;
			        sourceOriginOffsetX *= (OldScaleX / scaleX);
			        sourceOriginOffsetY *= (OldScaleY / scaleY);
		        }
	        }
#endif
#if false // this is the fast drawing path
	        if(!IsScaled
		        && !IsRotated
		        &&	x == (int)x
		        &&	y == (int)y)
		        //&&  (m_OptionalFlags & RenderCorrectDestAlpha) == 0)
	        {
		        bool SomethingToDraw = false;
		        double X = x - HotspotOffsetX;
		        double Y = x - HotspotOffsetY;
		        //g_NumPixelsBlitted += source.GetWidth() * source.GetHeight();

		        //static CBlitTransBlendMultColor TransBlendMultColor;
		        //static CBlitTransBlendAddColor  TransBlendAddColor;

#if false
                rect_d SourceRect;
		        source.GetClippingRect(&SourceRect);
		        //assert((SourceRect.top < SourceRect.bottom) && (SourceRect.left < SourceRect.right));
		        rect_d DestRect;
		        DestRect = SourceRect;
                DestRect.Offset((int)(X), (int)(Y));
		        if(renderingMode != BlendMode.DO_NOT_SET_DEST)
		        {
			        if(m_DoClipping)
			        {
				        //if(CFrame::ClipRects(&m_ClippingRect, &SourceRect, &DestRect))
				        {
					        SomethingToDraw = true;
				        }
			        }
			        else
			        {
				        SomethingToDraw = true;
			        }

			        if(SomethingToDraw)
			        {
                        /*
				        if(m_OptionalFlags & CBlitXParams::RenderOneMinusScrAlpha_One)
				        {
					        SomethingToDraw = pDestFrame.Blit(source, &SourceRect, &DestRect, &TransBlendAddColor);
				        }
				        else
				        {
					        if((g_BlitTransColorBlend & 0xFFFFFF) == 0xFFFFFF)
					        {
						        BlitTrans.SetOpacity((double)Color32.Alpha / 255.f);
						        SomethingToDraw = pDestFrame.Blit(source, &SourceRect, &DestRect, &BlitTrans);
						        BlitTrans.SetOpacity(1.f);
					        }
					        else
					        {
						        SomethingToDraw = pDestFrame.Blit(source, &SourceRect, &DestRect, &TransBlendMultColor);
					        }
				        }
                         */
			        }
		        }

		        m_OutFinalBlitBounds = DestRect;
#endif
            }
	        else
#endif
            {
#if false
		        if(	IsMipped)
		        {
			        HotspotOffsetX *= (inScaleX / scaleX);
			        HotspotOffsetY *= (inScaleY / scaleY);
		        }
#endif
                double FinalHotspotX = sourceOriginOffsetX - HotspotOffsetX;
                double FinalHotspotY = sourceOriginOffsetY - HotspotOffsetY;

                IImageByte oldDestImaeg = DestImage;
                if (renderingMode == BlendMode.FORCE_SOURC_ONE_DEST_ONE_MINUS_ALPHA_BLENDER)
                {
                    // TODO: make the blenders make more sense (this code does not look reasonable) [4/6/2009 lbrubaker]
                    ImageBuffer destImageWithPreMultBlender = new ImageBuffer();
                    switch (DestImage.BitDepth)
                    {
                        case 24:
                            destImageWithPreMultBlender.Attach(DestImage, new BlenderPreMultBGR());
                            break;

                        case 32:
                            if (color.A_Byte == 255)
                            {
                                destImageWithPreMultBlender.Attach(DestImage, new BlenderPreMultBGRA());
                            }
                            else
                            {
                                //destImageWithPreMultBlender.Attach(DestImage, new BlenderPreMultBGRA());
                                destImageWithPreMultBlender.Attach(DestImage, new BlenderPolyColorPreMultBGRA(color));
                            }
                            break;

                        default:
                            throw new Exception("Unknown bit depth");
                    }

                    ImageClippingProxy clipDestImageWithPreMultBlender = new ImageClippingProxy(destImageWithPreMultBlender);

                    m_DestImage = clipDestImageWithPreMultBlender;
                }

                DrawImage(source, x, y,
                    FinalHotspotX, FinalHotspotY,
                    scaleX, scaleY, agg_math.DegToRad(angleDegrees), color, ref m_OutFinalBlitBounds, true, false, useNearestNeighborFilter);
                m_DestImage = oldDestImaeg;

#if false
		        LineFloat(BoundingRect.left, BoundingRect.top, BoundingRect.right, BoundingRect.top, WHITE);
		        LineFloat(BoundingRect.right, BoundingRect.top, BoundingRect.right, BoundingRect.bottom, WHITE);
		        LineFloat(BoundingRect.right, BoundingRect.bottom, BoundingRect.left, BoundingRect.bottom, WHITE);
		        LineFloat(BoundingRect.left, BoundingRect.bottom, BoundingRect.left, BoundingRect.top, WHITE);
#endif
            }
        }

        public override void Render(IImageFloat source,
            double x, double y,
            double angleDegrees,
            double inScaleX, double inScaleY,
            RGBA_Floats color,
            BlendMode renderingMode)
        {
            throw new NotImplementedException();
        }

        public override void Clear(IColorType color)
        {
            ImageClippingProxy clipper = (ImageClippingProxy)m_DestImage;
            if (clipper != null)
            {
                clipper.clear(color);
            }
            else
            {
                ImageClippingProxyFloat clipperF = (ImageClippingProxyFloat)m_DestImageFloat;
                if (clipperF != null)
                {
                    clipperF.clear(color);
                }
            }
        }
    }
}
